#coding=utf-8
import requests,xlrd,re,os,sys,multiprocessing,threading,time,IPy
from multiprocessing import Pool
from tkinter import *
from tkinter.scrolledtext  import ScrolledText

def DDIPff():#获取调度IP列表,{CP:调度ip列表}
    print('正在获取调度信息')
    r=requests.get("http://chuli.watone.com.cn:8098/DispatchIP/ExportExcel?DispatchResourceName=&IP=").content#下载调度xls
    with open("DDIPcache","wb")  as f:#暂存在本地的DDIPcache文件
        f.write(r)
    DDIP=xlrd.open_workbook("DDIPcache")#读取DDIPcache
    #print(DDIP.sheet_names())
    DDIP_sh= DDIP.sheet_by_index(0)
    row_num=DDIP_sh.nrows
    #print(row_num)
    DDIP_Dict={}
    for x in range(row_num):#把数据修改成字典形式
        line=DDIP_sh.row_values(x)#[line]
        #DDIP_list.append(line)
        try:
            DDIP_Dict[line[0]].append(line[1])
        except:
            DDIP_Dict[line[0]]=[]
            DDIP_Dict[line[0]].append(line[1])
    #print(DDIP_list)
    return  DDIP_Dict

def workTD():#按下按钮触发的动作
    output1.delete(0.0,END)#清空输出文本框
    #global checkCPlist
    #global DDIP_Dict
    checkCPlist=[]#初始化要检查的CP列表
    for x in checkCP.curselection():#checkCP.curselection()是选中状态的checkbox值的序号
        checkCPlist.append(checkCP.get(x))#checkCP.get(x)通过序号获取内容，把选中状态的CP名称append到checkCPlist中，即要检测的CP列表
    var.set("开始检测%s,请勿操作" % str(checkCPlist))#修改提示文本，提示用户请勿在做操作
    checkipdict=checkList(checkCPlist,DDIP_Dict)#调用checkList函数，根据checkCPlist从字典中取出对应的ip地址列表
    print(checkipdict)
    th = threading.Thread(target=work, args=(checkipdict,))  # 创建后台线程，执行work函数
    th.setDaemon(True)  # 守护线程，gui线程为主线程在前台，调度检测的线程在后台，窗口在检测期间不会被锁死
    print("后台线程启动……")
    th.start()

def checkList(checkCP,DDIP_Dict):#根据checkCPlist从字典中取出对应的ip地址列表
    checkdict={}
    for x in checkCP:
        checkdict[x]=DDIP_Dict[x]
    return checkdict

def work(checkipdict):#后台线程检测调度IP
    try:
        Router=R.get()#获取关键路由的值

        with open('检测结果.txt', 'w') as f:#初始化结果输出文本，新建或覆盖原有内容
            f.writelines("%s\t%s\t%s\n" % ("CP", "IP", "status"))
    except:#提示关闭文本
        print("请先关闭 检测结果.txt")
        var.set("请先关闭 检测结果.txt")
        return 0#退出线程
    try:
        PNum = int(PoolNum.get())#获取同时并发进程数
        if PNum >=100:#限制最大并发数不超过100个防止死机
            raise NameError("进程数：请输入小于100的数字")
        P = Pool(PNum)#创建多个进程来进行检测，进程数自定义设置
        print("启动%d个tracert进程开始检测" % (PNum,))

        for cp in checkipdict:
            for ip in checkipdict[cp]:#遍历checkipdict中左右要检测的ip,以(cp名,ip地址，关键路由)传递给tracerIP来检测
                P.apply_async(tracerIP, (cp,ip,Router), callback=mycallback)#调用tracerIP检测，多进程方式，回调函数mycallback处理每个进程的返回值
        P.close()#关闭多进程
        P.join()
        print("complete!")
        var.set("运行完毕，保存在检测结果.txt")#修改提示文本提示用户检测结束
    except Exception as e:#发生异常时输出异常内容
        print("中断")
        var.set("错误中断")
        output1.insert(END, e)
        output1.see(END)
        try:#并尝试关闭多进程
            P.close()
            P.join()
        except:
            pass


def tracerIP(cp,ip,R):#cp名，ip地址，关键路由
    try:
        ipL=[]
        ipp=IPy.IP(ip)
        for x in ipp:
            ipL.append(x)
        ipx=str(ipp[ipp.len()//2])#通过IPy从ip段中取出网段中中间的地址来做测试
        RT = os.popen('tracert -d -w 200 %s' % ipx).read()#调用windows的tracert对调度ip进行路由跟踪
        traceRes=re.findall("\d+\.\d+\.\d+\.\d+",RT)#跟踪路径上的ip生成列表
        #print(ipx,traceRes,R)
        if IsHT(traceRes,R):#测试关键路由是否出现在跟踪路径上
            status = "successful"
        else:
            status = "failed"
        print('%s\t%s\t%s\n' % (cp,ip,status))
        return (cp, ip, status)
    except Exception as e:
        return (cp, ip, e)

def IsHT(Res,R):
    for x in Res:
        try:
            if x in IPy.IP(R):
                return True
        except:
            return FALSE
    return FALSE

def mycallback(x):#回调函数，处理每个子进程的返回值
    output1.insert(END,"%s\t%s\t%s\n" % x)#插入都输出框
    output1.see(END)#自动移动到当前值
    with open('检测结果.txt', 'a+') as f:#写入到文本
        f.writelines("%s\t%s\t%s\n" % x)
    time.sleep(0.001)#设置写入延迟


if  __name__ =='__main__':
    multiprocessing.freeze_support()#添加对多进程的支持
    try:
        checkCPlist = []#初始化要检测的CP列表
        DDIP_Dict = DDIPff()#调度IP字典，从调度平台下载调度表xls,生成{cp1:[ip1,ip2...],cp2:[ip1,ip2]}的格式
        CPlist = []#所有CP列表，从字典中提取key
        for x in DDIP_Dict:
            CPlist.append(x)
        CPlist.remove("资源名称")
        print("加载成功", CPlist)
    except:
        CPlist=["调度加载失败"]#加载发生错误时的输出
    root = Tk()#创建tk对象
    root.title('调度IP检测')#标题
    #root.geometry("500x500")
    ##row=0 以下是第0行的元素
    labRoute=Label(root,text='关键路由').grid(row=0,column=0,sticky=E,padx=5,pady=5)#定位在0行，0列，关键路由的标签文本
    router1=StringVar()#关键路由的变量申明
    R=Entry(root,textvariable=router1,width=40)#输入框，值为关键路由
    router1.set("30.207.0.0/16")#将关键路由的默认值设置为30.207.0.0/16，华通路由器互联地址
    R.grid(row=0,column=1,columnspan=2,sticky=W,padx=5,pady=5)#定位在第0行第1列，宽度为2列，左对齐，水平垂直边距均为5
    selectButton=Button(root,text="开始检测",command=workTD)#开始按钮，按下触发workTD()函数
    selectButton.grid(row=0,column=3,padx=5,pady=5,sticky=W)#定位在第0行第3列
    ##row=1 第1列的元素
    var = StringVar()#申明一个变量
    label = Label(root, font=('微软雅黑', 10), fg='red', textvariable=var)#定义一个文本标签，显示var变量的值，用来给交互
    label.grid(row=1,column=0,columnspan=2,padx=5,pady=5)#定位的第1行第0列，宽度为2列
    labPool = Label(root, text='进程数').grid(row=1, column=2, sticky=E, padx=5, pady=5)#进程数文本标签，定位在1行2列
    PN=StringVar()#申明默认进程数的变量
    PN.set("20")#设置默认值为20
    PoolNum = Entry(root, textvariable=PN, width=10)#进程数的文本框，默认值是PN
    PoolNum.grid(row=1,column=3,sticky=W,padx=5,pady=5)#定位在1行3列
    ##row=2 第二行的元素
    labelCP=Label(root,text="选择CP").grid(row=2,column=0,columnspan=2,padx=5,pady=5)#CP选择框的文本
    labelRes=Label(root,text="结果").grid(row=2,column=2,columnspan=2,padx=5,pady=5)#结果输出框的文本
    var.set("选择要检测的cp")#修改交互变量的值，提示用户选择CP
    ##row=3 第3列
    checkCP=Listbox(root,selectmode = MULTIPLE,height=30,width=35)#cp选择列表，使用Listbox，模式为多选
    for x in CPlist:#把cp列表插入到checkCP
        checkCP.insert(END,x)
    checkCP.grid(row=3,column=0,columnspan=2,padx=5,pady=5,sticky=N)#定位在3行
    output1 = ScrolledText(root, font=('微软雅黑', 10), fg='blue',bg='#F0F0F0',height=29,width=35)#结果输出框
    output1.grid(row=3,column=2,columnspan=2,padx=5,pady=5,sticky=N)#定位在第三行

    root.mainloop()#tk主循环



